package com.tanhua.dubbo.es.api;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.dubbo.config.annotation.Service;
import com.tanhua.dubbo.server.api.UserLocationApi;
import com.tanhua.dubbo.server.pojo.UserLocation;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.dubbo.server.vo.UserLocationVo;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@Service(version = "1.0.0")
@Slf4j
public class UserLocationApiImpl implements UserLocationApi {

    //注入es模板
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    /**
     * es与MySQL的名词对比
     * Elasticsearch:       index(索引)-->type(类型)-->document(文档)-->field(字段)
     * mysql:   database(数据库)-->table(表)-->row(行)-->line(列)
     * <p>
     * 1.先上一套保险,判断我们es中的索引是否存在,再判断类型是否存在,再判断文档是否存在
     * 1.1在判断索引和类型是否存在时,我们希望在导入依赖时就判断,由此加上@PostConstruct注解
     */
    @PostConstruct
    public void initIndex() {
        if (!elasticsearchTemplate.indexExists("tanhua")) {
            //如果索引不存在,就新创一个索引
            elasticsearchTemplate.createIndex(UserLocation.class);
        }

        if (!elasticsearchTemplate.typeExists("tanhua", "user_location")) {
            //如果类型不存在,就新创一个类型
            elasticsearchTemplate.putMapping(UserLocation.class);
        }

    }

    /**
     * 更新用户的地理位置
     *
     * @param userId    用户id
     * @param longitude 经度
     * @param latitude  纬度
     * @param address   地址名称
     * @return
     */
    @Override
    public Boolean updateUserLocation(Long userId, Double longitude, Double latitude, String address) {
        /*
            想要更新用户的地理位置,就要查询该用户是否存过文档,
            如果没有,就现存该用户的地理位置信息至类型中
            如果有,就更新该用户文档的部分字段
         */

        try {
            GetQuery getQuery= new GetQuery();
            getQuery.setId(String.valueOf(userId));
            UserLocation userLocation = elasticsearchTemplate.queryForObject(getQuery, UserLocation.class);
            if(ObjectUtil.isEmpty(userLocation)){
                //当userLocation不存在时,新建一个存入es中
                userLocation = new UserLocation();
                userLocation.setUserId(userId);
                userLocation.setAddress(address);
                userLocation.setCreated(System.currentTimeMillis());
                userLocation.setUpdated(userLocation.getCreated());
                userLocation.setLastUpdated(userLocation.getCreated());
                userLocation.setLocation(new GeoPoint(latitude,longitude));

                IndexQuery indexQuery = new IndexQueryBuilder().withObject(userLocation).build();
                elasticsearchTemplate.index(indexQuery);
            }else{
                //当userLocation存在时,更新文档的部分字段
                Map<String,Object> map = new HashMap<>();
                map.put("location",new GeoPoint(latitude,longitude));
                map.put("address",address);
                map.put("update",System.currentTimeMillis());
                map.put("lastUpdate",userLocation.getUpdated());
                UpdateRequest updateRequest = new UpdateRequest();//注意:"这里的UpdateRequest是es的,注意区别
                updateRequest.doc(map);

                UpdateQuery updateQuery = new UpdateQueryBuilder()
                                                                                    .withId(String.valueOf(userId))
                                                                                    .withClass(UserLocation.class)
                                                                                    .withUpdateRequest(updateRequest).build();



                elasticsearchTemplate.update(updateQuery);
            }
            return true;
        } catch (Exception e) {
            log.error("更新地理位置失败~ userId = " + userId + ", longitude = " + longitude + ", latitude = " + latitude + ", address = " + address, e);
        }
        return false;
    }

    @Override
    public UserLocationVo queryByUserId(Long userId) {
        GetQuery getQuery = new GetQuery();
        getQuery.setId(String.valueOf(userId));
        UserLocation userLocation = this.elasticsearchTemplate.queryForObject(getQuery, UserLocation.class);
        if(ObjectUtil.isNotEmpty(userLocation)){
            return UserLocationVo.format(userLocation);
        }

        return null;

    }

    /**
     * 查询用户信息
     * @param longitude 经度
     * @param latitude  纬度
     * @param distance  距离(米)
     * @param page      页数
     * @param pageSize  页面大小
     * @return
     */
    @Override
    public PageInfo<UserLocationVo> queryUserFromLocation(Double longitude, Double latitude, Double distance, Integer page, Integer pageSize) {
        /*
        1.查询该用户的地理位置
        2.以该用户的地理位置为圆心,指定半径查询
         */
        PageInfo<UserLocationVo> pageInfo = new PageInfo<>();
        pageInfo.setPageNum(page);
        pageInfo.setPageSize(pageSize);

        String fieldName = "location";

        //实现了SearchQuery接口，构造分页、排序
        NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();

        //分页
        PageRequest pageRequest = PageRequest.of(page - 1, pageSize);
        searchQueryBuilder.withPageable(pageRequest);

        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        //以一个点为中心，指定范围查询
        GeoDistanceQueryBuilder geoDistanceQueryBuilder = new GeoDistanceQueryBuilder(fieldName);
        //中心点
        geoDistanceQueryBuilder.point(new GeoPoint(latitude, longitude));
        //距离（画圆的半径）单位：公里
        geoDistanceQueryBuilder.distance(distance / 1000, DistanceUnit.KILOMETERS);

        boolQueryBuilder.must(geoDistanceQueryBuilder);
        searchQueryBuilder.withQuery(boolQueryBuilder);

        //排序，由近到远排序
        GeoDistanceSortBuilder geoDistanceSortBuilder = new GeoDistanceSortBuilder(fieldName, latitude, longitude);
        geoDistanceSortBuilder.order(SortOrder.ASC); //正序排序
        geoDistanceSortBuilder.unit(DistanceUnit.KILOMETERS); //设置单位
        searchQueryBuilder.withSort(geoDistanceSortBuilder);

        AggregatedPage<UserLocation> aggregatedPage = this.elasticsearchTemplate.queryForPage(searchQueryBuilder.build(), UserLocation.class);
        if(CollUtil.isEmpty(aggregatedPage.getContent())){
            return pageInfo;
        }

        pageInfo.setRecords(UserLocationVo.formatToList(aggregatedPage.getContent()));

        return pageInfo;
    }
}
